###########################################################################
#       Copyright (C) 2008 Timothy Clemans <timothy.clemans@gmail.com>
#
#   Released under the New BSD license.
#
###########################################################################

import pre_mod
import representations

class Table(object):
    """Defines a table given a list of lists.
    
    EXAMPLES:  
        >>> Table([(i, i**2, i**3) for i in range(1, 4)])
        1 | 1 | 1
        2 | 4 | 8
        3 | 9 | 27
        >>> Table([(i, i**3, i**2) for i in range(1, 4)])
        1 |  1 | 1
        2 |  8 | 4
        3 | 27 | 9
        >>> Table([[5, 8, -2, 7, 20], [2 * i for i in range(5)]])
        5 | 8 | -2 | 7 | 20
        0 | 2 |  4 | 6 |  8
        >>> Table([[5, 8, -2, 7, 20], [2 * i for i in range(5)]], direction='col')
         5 | 0
         8 | 2
        -2 | 4
         7 | 6
        20 | 8
        >>> Table([range(i) for i in range(1, 5)])
        0 |   |   |
        0 | 1 |   |
        0 | 1 | 2 |
        0 | 1 | 2 | 3
        >>> Table([range(i) for i in [5, 1, 3, 5]])
        0 | 1 | 2 | 3 | 4
        0 |   |   |   |
        0 | 1 | 2 |   |
        0 | 1 | 2 | 3 | 4
        >>> Table([['A', 'B']] + [[None, None]] * 5 + [['C', 'D']])
        A | B
          |
          |
          |
          |
          |
        C | D
    """
    def __init__(self, data, direction='row'):
        self.data = pre_mod.make_pre_mod(data, direction)
        self.string_representation = representations.string.StringRepresentation(self)
        self.dimensions = (len(data), max(map(len, data)))
        
    def __repr__(self):
        return self.string_representation.__repr__()
    
    def cell(self, row, col):
        """Returns cell in table given a row and column.
        
        Note: Row and column numbering starts at zero.
        
        EXAMPLES:
            >>> g = Table([(i, i**3, i**2) for i in range(1, 4)]); g
            1 |  1 | 1
            2 |  8 | 4
            3 | 27 | 9
            >>> g.cell(0, 1)
            1
            >>> g.cell(2, 2)
            9
            >>> g.cell(2, 0)
            3
            >>> g.cell(3, 2)
            Traceback (most recent call last):
            IndexError: Row 3 is not in table.
            >>> g.cell(50, 50)
            Traceback (most recent call last):
            IndexError: Row 50 and column 50 are not in table.
            >>> g.cell(0, 23)
            Traceback (most recent call last):
            IndexError: Column 23 is not in table.
        """
        try:
            return self.data[row][col]
        except IndexError:
            r, c = self.dimensions
            r -= 1
            c -= 1
            if row > r and col > c:
                raise IndexError, 'Row %d and column %d are not in table.' % (row, col)
            elif row > r:
                raise IndexError, 'Row %d is not in table.' % row
            else:
                raise IndexError, 'Column %d is not in table.' % col
    
    def row(self, n):
        """Returns nth row in table.
        
        Note: Row numbering starts at zero.
        
        EXAMPLES:
            >>> g = Table([(i, i**3, i**2) for i in range(1, 4)]); g
            1 |  1 | 1
            2 |  8 | 4
            3 | 27 | 9
            >>> g.col(0)
            [1, 2, 3]
            >>> g.col(1)
            [1, 8, 27]
            >>> g.col(2)
            [1, 4, 9]        
        """
        return self.data[n]
    
    def col(self, n):
        """Returns nth column in table.
        
        Note: Column numbering starts at zero.
        
        EXAMPLES:
            >>> g = Table([(i, i**3, i**2) for i in range(1, 4)]); g
            1 |  1 | 1
            2 |  8 | 4
            3 | 27 | 9
            >>> g.col(0)
            [1, 2, 3]
            >>> g.col(1)
            [1, 8, 27]
            >>> g.col(2)
            [1, 4, 9]        
        """
        return list(zip(*self.data)[n])
    
    
class Row(Table):
    """Defines a row given a list.
    
    EXAMPLES:
        >>> Row([i**2 for i in range(1, 10)])
        1 | 4 | 9 | 16 | 25 | 36 | 49 | 64 | 81
        >>> import string; Row(string.hexdigits[::2])
        0 | 2 | 4 | 6 | 8 | a | c | e | A | C | E
    """
    def __init__(self, data):
        Table.__init__(self, [list(data)])
        
    def __add__(self, other):
        """Adds row with Row or Table. 
        
        EXAMPLES:
            >>> Row(range(5)) + Row(range(3))
            0 | 1 | 2 | 3 | 4
            0 | 1 | 2 |   |
            >>> Row(['Apple', 'Blueberry', 'Orange']) + Row(['Rocket', 'Car', 'Boat'])
             Apple | Blueberry | Orange
            Rocket |       Car |   Boat
        """
        if isinstance(other, Row):
            return Table([[i.data for i in self.data[0]], [i.data for i in other.data[0]]])
        elif isinstance(other, Table):
            return 
        else:
            raise TypeError, 'can only concatenate Row or Table (not "%s") to Row' % str(type(other))[7:-2]

    
class Column(Table):
    """Defines a column given a list.
    
    EXAMPLES:
        >>> Column(range(1, 20, 3))
         1
         4
         7
        10
        13
        16
        19
        >>> import string; Column(string.lowercase[::4])
        a
        e
        i
        m
        q
        u
        y
        >>> Column(['Apple', 'Orange', 'Cherry', 'Lemon', 'Strawberry'])
             Apple
            Orange
            Cherry
             Lemon
        Strawberry
    """
    def __init__(self, data):
        data = list(data)
        Table.__init__(self, [[_] for _ in data])

Col = Column